package com.fenqing.core.design;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import com.fenqing.annotations.DesignClass;
import lombok.Data;
import lombok.SneakyThrows;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author fenqing
 */
public abstract class DesignTopic {

    private final Class<?> clazz;
    private Object instance;

    @Data
    private static class MethodParam {

        public static final int TYPE_METHOD = 0, TYPE_CONSTRUCTOR = 1;

        int type;
        Constructor<?> constructor;
        Method method;
        List<Object> params;
    }

    public List<MethodParam> invokeMap = new ArrayList<>();

    @SuppressWarnings("all")
    public DesignTopic(String methods, String params){
        this.clazz = getDesign();
        Method[] methods1 = clazz.getMethods();
        Constructor<?>[] constructors = clazz.getConstructors();
        Map<String, List<Method>> listMap = Arrays.stream(methods1).collect(Collectors.groupingBy(Method::getName));
        JSONArray method = JSONUtil.parseArray(methods);
        JSONArray param = JSONUtil.parseArray(params);
        methodParamMapping(listMap, constructors, (List) method, (List)param);
    }

    public Class<?> getDesign(){
        Class<? extends DesignTopic> aClass = this.getClass();
        Class<?>[] classes = aClass.getClasses();
        for (Class<?> aClass1 : classes) {
            DesignClass annotation = aClass1.getAnnotation(DesignClass.class);
            if (annotation != null) {
                return aClass1;
            }
        }
        return null;
    }

    private void methodParamMapping(Map<String, List<Method>> listMap, Constructor<?>[] constructors, List<String> method, List<List<Object>> params){
        CollUtil.forEach(method, (item, index) -> {
            MethodParam methodParam = new MethodParam();
            if(listMap.containsKey(item)){
                methodParam.type = MethodParam.TYPE_METHOD;
                methodParam.method = listMap.get(item).get(0);
                methodParam.params = params.get(index);
            }else{
                methodParam.type = MethodParam.TYPE_CONSTRUCTOR;
                methodParam.constructor = Arrays.stream(constructors).findFirst().get();
                //转换
                List<Object> o = typeConversion(methodParam.constructor.getParameters(), params.get(index));
                methodParam.params = o;
            }
            invokeMap.add(methodParam);
        });
    }

    private List<Object> typeConversion(Parameter[] parameters, List<Object> objects) {
        List<Object> result = new ArrayList<>();
        for (int i = 0; i < parameters.length; i++) {
            Object o = objects.get(i);
            result.add(typeConversion(parameters[i], o));
        }
        return result;
    }

    private Object typeConversion(Parameter parameter, Object value) {
        Type parameterizedType = parameter.getParameterizedType();
        if (parameterizedType.equals(int[][].class)) {
            return JSONUtil.parseArray(value).toArray(int[][].class);
        }
        return value;
    }

    @SneakyThrows
    public String execute(){
        List<Object> result = new ArrayList<>();
        for (MethodParam methodParam : invokeMap) {
            if(methodParam.type == MethodParam.TYPE_METHOD){
                result.add(methodParam.method.invoke(instance, methodParam.params.toArray()));
            }else{
                methodParam.constructor.setAccessible(true);
                instance = methodParam.constructor.newInstance(methodParam.params.toArray());
                result.add(null);
            }
        }
        JSONConfig config = new JSONConfig();
        config.setIgnoreNullValue(false);
        return JSONUtil.toJsonStr(result, config);
    }

    public String run(){
        long start = System.nanoTime();
        long start1 = System.currentTimeMillis();
        String execute = execute();
        long end = System.nanoTime();
        long end1 = System.currentTimeMillis();
        System.out.println("耗时：" + (end - start) + "ns");
        System.out.println("耗时：" + (end1 - start1) + "ms");
        return execute;
    }

}
